/*
 * Description: 用于封装socket，提供基础通信能力。
 */
#include "guest_socket.h"
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <string>
#include <cstddef>
#include <mutex>
#include <stdexcept>
#include <thread>
#include <chrono>
#include <algorithm>
#include <sys/types.h>
#include <sys/stat.h>
#include <winsock2.h>
#include <Ws2tcpip.h>
#pragma comment(lib, "WS2_32")
#include <basetyps.h>
#include <wtypes.h>
#include <initguid.h>
#include <conio.h>
#include <setupapi.h>
#include <winioctl.h>
#include "log.h"

namespace {
#if (defined _WIN32)
void GetOverLapperResult(HANDLE m_hDevice, int res, OVERLAPPED &overLapped, DWORD &bytes)
{
    if (!res && GetLastError() == ERROR_IO_PENDING) {
        res = GetOverlappedResult(m_hDevice, &overLapped, &bytes, TRUE);
    } else {
        LOG_ERR("sock read/write failed: Error = " FORMAT(d) ", res = " FORMAT(d), GetLastError(), res);
    }
    CloseHandle(overLapped.hEvent);
}
#endif
}

namespace COMMON {
#if (defined _WIN32)
constexpr int MAX_SERIAL_NUM = 32;

#define IOCTL_GET_INFORMATION    CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)

DEFINE_GUID(GUID_VIOSERIAL_PORT, 0x6fde7521, 0x1b65, 0x48ae, 0xb6, 0x28, 0x80, 0xbe, 0x62, 0x1, 0x60, 0x26);

using VIRTIO_PORT_INFO = struct _tagVirtioPortInfo {
    unsigned int id;
    BYTE OutVqFull;
    BYTE HostConnected;
    BYTE GuestConnected;
    std::string Name;
};
using PVIRTIO_PORT_INFO = VIRTIO_PORT_INFO*;
#endif

MsgSock::MsgSock()
{
}

MsgSock::~MsgSock()
{
    LOG_DEBUG("MsgSock::~MsgSock()");
    Destroy();
    LOG_DEBUG("MsgSock::~MsgSock() end");
}

#if (defined _WIN32)
PSP_DEVICE_INTERFACE_DETAIL_DATA GetDevicePath(unsigned int index, LPGUID InterfaceGuid)
{
    LOG_DEBUG("GetDevicePath, index = " FORMAT(u), index);
    HDEVINFO HardwareDeviceInfo = SetupDiGetClassDevs(InterfaceGuid, nullptr,
        nullptr, (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));
    if (HardwareDeviceInfo == INVALID_HANDLE_VALUE) {
        LOG_ERR("Cannot get class devices");
        return nullptr;
    }

    SP_DEVICE_INTERFACE_DATA DeviceInterfaceData;
    DeviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

    int bResult = SetupDiEnumDeviceInterfaces(HardwareDeviceInfo, nullptr, InterfaceGuid, index, &DeviceInterfaceData);
    if (bResult == FALSE) {
        LOG_ERR("Cannot get enumerate device interfaces, err:" FORMAT(lu), GetLastError());
        SetupDiDestroyDeviceInfoList(HardwareDeviceInfo);
        return nullptr;
    }

    unsigned long requiredLength = OK;
    SetupDiGetDeviceInterfaceDetail(HardwareDeviceInfo, &DeviceInterfaceData, nullptr, OK, &requiredLength, nullptr);

    PSP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)LocalAlloc(
        LMEM_FIXED, requiredLength);
    if (DeviceInterfaceDetailData == nullptr) {
        LOG_ERR("Cannot allocate memory");
        SetupDiDestroyDeviceInfoList(HardwareDeviceInfo);
        return nullptr;
    }

    DeviceInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

    unsigned long length = requiredLength;

    bResult = SetupDiGetDeviceInterfaceDetail(HardwareDeviceInfo, &DeviceInterfaceData, DeviceInterfaceDetailData,
        length, &requiredLength, nullptr);
    if (bResult == FALSE) {
        LOG_ERR("Cannot get device interface details");
        SetupDiDestroyDeviceInfoList(HardwareDeviceInfo);
        LocalFree(DeviceInterfaceDetailData);
        return nullptr;
    }

    SetupDiDestroyDeviceInfoList(HardwareDeviceInfo);
    return DeviceInterfaceDetailData;
}

int MsgSock::GetInfoTest()
{
    LOG_DEBUG("MsgSock::GetInfoTest!");
    size_t len = sizeof(VIRTIO_PORT_INFO);
    PVOID buf = GlobalAlloc(OK, len);
    if (buf == nullptr) {
        return -1;
    }
    GetInfo(buf, &len);

    PVIRTIO_PORT_INFO inf = (PVIRTIO_PORT_INFO)buf;
    LOG_INFO("id = " FORMAT(d), inf->id);
    int ret = inf->id;
    GlobalFree(buf);
    return ret;
}

int MsgSock::GetInfo(void* buf, size_t *size)
{
    LOG_DEBUG("MsgSock::GetInfo!");
    DWORD ulOutLength = static_cast<DWORD>(*size);
    ULONG ulReturnedLength = OK;

    int res = DeviceIoControl(m_hDevice, IOCTL_GET_INFORMATION, nullptr, OK, buf, ulOutLength, &ulReturnedLength,
        nullptr);
    if (!res) {
        DWORD err = GetLastError();
        if (err != ERROR_MORE_DATA) {
            LOG_ERR("Ioctl failed with code " FORMAT(lu), err);
        }
    }

    *size = ulReturnedLength;
    return res;
}

int MsgSock::WindowsSerialInit(int port, int timeout, bool ovrl)
{
    LOG_DEBUG("MsgSock::WindowsSerialInit!");
    PWCHAR devicePath = nullptr;
    isOverlapped = ovrl;
    int retryTimes = 0;
    do {
        for (int ifIndex = 0; ifIndex < MAX_SERIAL_NUM; ifIndex++) {
            PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterface = GetDevicePath(ifIndex, (LPGUID)&GUID_VIOSERIAL_PORT);
            if (deviceInterface == nullptr) {
                continue;
            }
            devicePath = reinterpret_cast<PWCHAR>(deviceInterface->DevicePath);
            if (devicePath == nullptr) {
                LOG_ERR("devicePath is null");
                continue;
            }
            // 枚举参数赋能
            m_hDevice = CreateFile(reinterpret_cast<LPCSTR>(devicePath), GENERIC_WRITE | GENERIC_READ, 0, nullptr,
                OPEN_EXISTING, ovrl ? FILE_FLAG_OVERLAPPED : FILE_ATTRIBUTE_NORMAL, nullptr);
            if (m_hDevice == INVALID_HANDLE_VALUE) {
                LOG_WARN("m_hDevice is INVALID, err:" FORMAT(lu), GetLastError());
                LocalFree(deviceInterface);
                continue;
            }
            LocalFree(deviceInterface);

            int id = GetInfoTest();
            if ((id - 1) != port) {
                CloseHandle(m_hDevice);
                m_hDevice = INVALID_HANDLE_VALUE;
                continue;
            }

            LOG_INFO("GetInfoTest ok port = {}", port);
            return OK;
        }
        if (m_hDevice == INVALID_HANDLE_VALUE) {
            LOG_WARN("Cannot find vioserial device, will retry");
            retryTimes++;
            Sleep(200); // 等待200ms后重试
        }
    } while (m_hDevice == INVALID_HANDLE_VALUE && retryTimes < RETRY_NUMBER);

    DWORD err = GetLastError();
    LOG_ERR("Cannot find vioserial device, error = " FORMAT(lu), err);
    return DEFAULT_ERROR;
}

int MsgSock::WindowsSend(const void *buffer, uint32_t len)
{
    LOG_DEBUG("MsgSock::WindowsSend!");
    DWORD writeBytes = 0;
    DWORD bytes = len;

    if (isOverlapped) {
        OVERLAPPED overLapped = { 0 };
        overLapped.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
        if (!overLapped.hEvent) {
            LOG_ERR("Failed to create event for overlap while send");
            return writeBytes;
        }
        std::unique_lock<std::mutex> lk(m_sendMutex);
        int syncRes = WriteFile(m_hDevice, buffer, bytes, &writeBytes, &overLapped);
        GetOverLapperResult(m_hDevice, syncRes, overLapped, writeBytes);
        lk.unlock();
    } else {
        int normalRes = WriteFile(m_hDevice, buffer, bytes, &writeBytes, NULL);
        if (!normalRes) {
            LOG_ERR("Cannot write vioserial device, Error = " FORMAT(lu), GetLastError());
        } else if (writeBytes != bytes) {
            LOG_ERR("Write vioserial device error. writeSize = " FORMAT(lu) " expected = " FORMAT(lu),
                writeBytes, bytes);
        }
    }
    return writeBytes;
}

int MsgSock::WindowsRecv(void *buffer, uint32_t len, const uint32_t timeout)
{
    LOG_DEBUG("MsgSock::WindowsRecv!");
    DWORD readBytes = 0;
    DWORD bytes = len;
    std::fill_n(static_cast<int8_t *>(buffer), bytes, 0);

    if (isOverlapped) {
        OVERLAPPED overLapped = { 0 };
        overLapped.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
        if (!overLapped.hEvent) {
            LOG_ERR("Failed to create event for overlap while recv");
            return readBytes;
        }
        int syncRes = ReadFile(m_hDevice, buffer, bytes, &readBytes, &overLapped);
        GetOverLapperResult(m_hDevice, syncRes, overLapped, readBytes);
    } else {
        int normalRes = ReadFile(m_hDevice, buffer, bytes, &readBytes, NULL);
        if (!normalRes) {
            LOG_ERR("PerformRead: ReadFile failed: Error " FORMAT(lu), GetLastError());
        } else if (readBytes != bytes) {
            LOG_ERR("Read vioserial device error. readSize = " FORMAT(lu) " expected = " FORMAT(lu), readBytes, bytes);
        }
    }
    return readBytes;
}

int MsgSock::WindowsClose()
{
    LOG_INFO("MsgSock::WindowsClose run!");
    int ret = 0;
    if (m_sockFd >= OK) {
        ret = closesocket(m_sockFd);
        m_sockFd = DEFAULT_ERROR;
    }
    if (m_hDevice != INVALID_HANDLE_VALUE) {
        CancelIoEx(m_hDevice, NULL);
        ret = CloseHandle(m_hDevice);
        m_hDevice = INVALID_HANDLE_VALUE;
    }
    LOG_INFO("MsgSock::WindowsClose! ret = " FORMAT(d), ret);
    return ret == 0 ? DEFAULT_ERROR : OK;
}
#endif

int MsgSock::SerialInit(SocketInfo info, int timeout)
{
    return WindowsSerialInit(info.port, timeout);
}

int MsgSock::Create(SocketInfo info, int maxNum)
{
    LOG_INFO("MsgSock::Create!");
    return SerialInit(info, RETRY_NUMBER);
}

std::shared_ptr<MsgSock> MsgSock::AcceptEx()
{
    LOG_DEBUG("MsgSock::AcceptEx!");
    std::shared_ptr<MsgSock> subMsgSock = nullptr;
    try {
        subMsgSock = std::make_unique<MsgSock>();
    } catch (const std::exception &e) {
        LOG_ERR("make unique error!");
        return nullptr;
    }
    subMsgSock->m_sockFd = static_cast<int>(m_sockFd);
    subMsgSock->m_hDevice = static_cast<void *>(m_hDevice);
    subMsgSock->sockType = sockType;

    LOG_DEBUG("AcceptEx ok, fd = " FORMAT(d), m_sockFd);
    return subMsgSock;
}

int MsgSock::Send(const void *buffer, uint32_t len)
{
    LOG_DEBUG("MsgSock::Send! len = " FORMAT(u), len);
    if (buffer == nullptr) {
        LOG_ERR("send buffer is nullptr");
        return INVALID_MSGSOCK_BUFFER;
    }
    return WindowsSend(buffer, len);
}

int MsgSock::Recv(void *buffer, uint32_t len, const uint32_t timeout)
{
    LOG_DEBUG("MsgSock::Recv! len = " FORMAT(u), len);
    if (buffer == nullptr) {
        LOG_ERR("recv buffer is nullptr");
        return INVALID_MSGSOCK_BUFFER;
    }
    return WindowsRecv(buffer, len, timeout);
}

int MsgSock::Close()
{
    return WindowsClose();
}

void MsgSock::Destroy()
{
    LOG_DEBUG("MsgSock::Destroy!");
    if (m_sockFd >= OK) {
        closesocket(m_sockFd);
        m_sockFd = DEFAULT_ERROR;
    }
    if (m_hDevice != INVALID_HANDLE_VALUE) {
        CancelIoEx(m_hDevice, NULL);
        CloseHandle(m_hDevice);
        m_hDevice = INVALID_HANDLE_VALUE;
    }
    LOG_DEBUG("MsgSock::Destroy end");
}

MsgSockInfo MsgSock::GetSockInfo()
{
    MsgSockInfo info;
    info.sockFd = m_sockFd;
    info.localAddr = m_localAddr;
    info.remoteAddr = m_remoteAddr;
    return info;
}

void MsgSock::SetRecvTimeout(uint32_t timeout) const
{
    LOG_DEBUG("MsgSock::SetRecvTimeout!");
    struct timeval timeValue = {};
    timeValue.tv_sec = timeout;
    timeValue.tv_usec = OK;
    if (setsockopt(m_sockFd, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeValue),
        sizeof(timeValue)) != OK) {
        LOG_ERR("set recv timeout failed, timeout: " FORMAT(u) " errno: " FORMAT(d), timeout, WSAGetLastError());
    }
}
}
